/*
 * SWFDecoder.java
 * Describe SWF
 *
 * A browser for Flash files.
 * Copyright (c) 2001-2006 Flagstone Software Ltd. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 * 
 * For more information please contact:
 * 
 * Stuart MacKay
 * Flagstone Software Ltd
 * 92 High Street
 * Wick, Caithness KW1 4LY
 * Scotland
 * 
 * www.flagstonesoftware.com
 */
package com.flagstone.describe;

final class SWFDecoder 
{
    /*
     * Index values for the context array in Decoder.
     */
    static final int Action = 0;
    static final int Version = 1;
    static final int Type = 2;
    static final int TransparentColors = 3;
    static final int NumberOfFillBits = 4;
    static final int NumberOfLineBits = 5;
    static final int NumberOfAdvanceBits = 6;
    static final int NumberOfGlyphBits = 7;
    static final int ArrayCountExtended = 8;
    static final int WideCodes = 9;

    int[] context = new int[10];

    private MovieListener listener = null;   
    private byte[] data = null;
    private int ptr = 0;
    private int end = 0;
    
    public SWFDecoder(byte[] bytes, MovieListener aListener)
    {
        for (int i=0; i<context.length; i++)
            context[i] = 0;
            
        listener = aListener;

        data = bytes;
        end = data.length << 3;
    }
        
    public int getPointer()
    {
        return ptr;
    }
    public void setPointer(int location)
    {
        ptr = location;
    }
    public void adjustPointer(int offset)
    {
        ptr += offset;
    }
    public void alignToByte()
    {
        ptr += (ptr % 8 > 0) ? 8 - (ptr % 8) : 0;
    }
    public void beginObject(String name, String type)
    {
        listener.recordMovieEvent(MovieListener.Begin, name, ptr, 0, type);
    }
    public void endObject(String name)
    {
        listener.recordMovieEvent(MovieListener.End, null, ptr, 0, name);
    }
    public void logValue(String name, Object anObject, int location, int numberOfBits)
    {
        listener.recordMovieEvent(MovieListener.Value, name, location, numberOfBits, anObject);
    }
    public void logError(String errorKey, int location, int length)
    {
        listener.recordMovieEvent(MovieListener.Error, null, location, length, errorKey);
    }
    public int scanBits(int numberOfBits, boolean signed)
    {
        int value = 0;
        int start = ptr;

        value = _readBits(numberOfBits, signed);
        ptr = start;
        
        return value;
    }
    public int scanWord(int numberOfBytes, boolean signed)
    {
        int value = 0;
        int start = ptr;

        value = _readWord(numberOfBytes*8, signed);
        ptr = start;
        
        return value;
    }
    public boolean readBool(String name)
    {
        int location = ptr;
        int value = _readBits(1, false);
        
        logValue(name, new Integer(value), location, 1);
    
        return value != 0;
    }
    public int readBits(String name, int numberOfBits, boolean signed)
    {
        int location = ptr;
        int value = 0;

        value = _readBits(numberOfBits, signed);        
        logValue(name, new Integer(value), location, numberOfBits);
    
        return value;
    }
    public int readWord(String name, int numberOfBytes, boolean signed)
    {
        int location = ptr;
        int value = 0;

        value = _readWord(numberOfBytes*8, signed);        
        logValue(name, new Integer(value), location, numberOfBytes*8);
    
        return value;
    }
    public int readFixedWord(String name, int size, int divisor, boolean signed)
    {
        int location = ptr;
        int value = 0;

        value = _readWord(size, signed);        
        logValue(name, new Float(value/divisor), location, size);
    
        return value;
    }
    public float readShortFixed(String name, int numberOfBits)
    {
        int location = ptr;
        float value = 0;
    
        value = ((float)_readBits(numberOfBits, true)) / 256.0f;
        logValue(name, new Float(value), location, numberOfBits);
        
        return value;
    }
    public float readFixed(String name, int numberOfBits)
    {
        float value = 0;
    
        value = ((float)_readBits(numberOfBits, true)) / 65536.0f;

        logValue(name, new Float(value), ptr-numberOfBits, numberOfBits);
        
        return value;
    }
    public double readDouble(String name)
    {
        int location = ptr;
        double value = 0.0;
    
        int upperInt = _readWord(32, false);
        int lowerInt = _readWord(32, false);
    
        long longValue = 0;
    
        longValue |= (long)upperInt << 32;
        longValue |= (long)lowerInt & 0x00000000FFFFFFFFL;
    
        value = Double.longBitsToDouble(longValue);

        logValue(name, new Double(value), location, 64);

        return value;
    }
    public int readBytes(String name, int numberOfBytes)
    {
        int location = ptr;
        int bytesRead = numberOfBytes;
    
        adjustPointer(numberOfBytes*8);
    
        logValue(name, "<Data>", location, bytesRead << 3);

        return bytesRead;
    }
    public int readString(String name, int length)
    {
        if (length == 0)
            return 0;
            
        int location = ptr;
        String value = null;
        byte[] str = new byte[length];
    
        int len = _readBytes(str);
        
        try { 
            value = new String(str, 0, len, "UTF8"); 
        }
        catch (java.io.UnsupportedEncodingException e) 
        {
            value = "";
        }
        
        while (value.charAt(value.length()-1) == 0) {
            value = value.substring(0, value.length()-1);
        }

        logValue(name, value, location, length<<3);

        return length;
    }
    public int readString(String name)
    {
        int location = ptr;
        String value = null;
    
        int start = ptr>>3;
        int length = 0;
    
        while (start < data.length && data[start++] != 0) length++;
    
        byte[] str = new byte[length];
        int len = _readBytes(str);

        try { 
            value = new String(str, 0, len, "UTF8"); 
        }
        catch (java.io.UnsupportedEncodingException e) 
        {
            value = "";
        }

        _readWord(8, false);
        len++;
    
        logValue(name, value, location, len<<3);

        return len;
    }
    private int _readBits(int numberOfBits, boolean signed)
    {
        int value = 0;
    
        if (numberOfBits == 0)
            return value;
        
        if (ptr >= end)
            return 0;
            
        try 
        {
            int index = ptr >> 3;
            int base = (data.length - index > 4) ? 0  : (4 - (data.length - index))*8;
            
            for (int i=32; i>base; i-=8, index++)
                value |= (data[index] & 0x000000FF) << (i-8);
    
            value <<= ptr % 8;
    
            if (signed)
                value >>= 32 - numberOfBits;
            else 
                value >>>= 32 - numberOfBits;
    
            adjustPointer(numberOfBits);
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
            logError("DataReadOverflow", ptr, numberOfBits);
            value = 0;
        }
        return value;
    }
    private int _readWord(int numberOfBits, boolean signed)
    {
        int value = 0;
    
        if (numberOfBits == 0)
            return value;

        if (ptr >= end)
            return 0;
              
        try 
        {
            int index = ptr >> 3;
    
            for (int i=0; i<numberOfBits; i+=8, ptr+=8, index++)
                value += (data[index] & 0x000000FF) << i;
            
            if (signed)
            {
                value <<= 32 - numberOfBits;
                value >>= 32 - numberOfBits;
            }
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
            logError("DataReadOverflow", ptr, numberOfBits);
            value = 0;
        }
        return value;
    }
    private int _readBytes(byte[] bytes)
    {
        int bytesRead = 0;
    
        if (bytes == null || bytes.length == 0)
            return bytesRead;

        if (ptr >= end)
            return bytesRead;
            
        try 
        {
            int index = ptr >>> 3;
            int numberOfBytes = bytes.length;
    
            for (int i=0; i<numberOfBytes; i++, ptr+=8, index++, bytesRead++)
                bytes[i] = data[index];
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
            logError("DataReadOverflow", ptr, bytes.length << 3);
        }       
        return bytesRead;
    }
}

